home *** CD-ROM | disk | FTP | other *** search
Wrap
Text File | 1994-04-16 | 21.0 KB | 805 lines | [ TEXT/PJMM]
unit MyTextDisplay; interface type LongArray = array[1..100000] of longInt; LongArrayPtr = ^LongArray; LongArrayHandle = ^LongArrayPtr; MyTextDisplayRecord = record { You can change these and the call resize/recalc } leading: integer; width: integer; leave_room_for_grow: boolean; { You can read these } full_rect: rect; view: rect; view_lines: longInt; total_length: longInt; view_width: integer; top_line: longInt; selStart, selEnd: longInt; hoffset: integer; window: WindowPtr; hcontrol, vcontrol: ControlHandle; font: integer; size: integer; fi: FontInfo; line_height: longInt; rn: integer; lines: longInt; { You should ignore these } last_click_time: longInt; last_click_offset: longInt; double_click: boolean; offsets: LongArrayHandle; end; LongPoint = record v: longInt; h: longInt; end; procedure MTDCreate (var mtd: MyTextDisplayRecord; window: WindowPtr; rn: integer; width: integer; hcontrol: boolean); procedure MTDDestroy (var mtd: MyTextDisplayRecord); procedure MTDSetPort (var mtd: MyTextDisplayRecord); procedure MTDSetFontSize (var mtd: MyTextDisplayRecord; font, size: integer); procedure MTDRecalculate (var mtd: MyTextDisplayRecord; justappend: boolean); procedure MTDDisplay (var mtd: MyTextDisplayRecord; draw_region: RgnHandle; fromline: longInt); procedure MTDSetSelection (var mtd: MyTextDisplayRecord; start, fin: longInt); procedure MTDGetSelectionData (var mtd: MyTextDisplayRecord; h: handle); procedure MTDResize (var mtd: MyTextDisplayRecord; view: rect); procedure MTDDoKey (var mtd: MyTextDisplayRecord; ch: char); procedure MTDDoClick (var mtd: MyTextDisplayRecord; var er: EventRecord); procedure MTDSetMouse (var mtd: MyTextDisplayRecord); procedure MTDScroll (var mtd: MyTextDisplayRecord; scroll: LongPoint); implementation uses Script, MyTypes, MyMathUtils, MyFileSystemUtils; const invis = 0; vis = 255; HiliteMode = $938; procedure SectRectRgn (rgn: RgnHandle; r: rect); var rrgn: RgnHandle; begin rrgn := NewRgn; RectRgn(rrgn, r); SectRgn(rgn, rrgn, rgn); DisposeRgn(rrgn); end; function MyFSReadChunkPos (refnum: integer; pos: longInt; len: integer; var s: str255): OSErr; var pb: ParamBlockRec; err: OSErr; begin if len > 255 then len := 255; pb.ioRefNum := refnum; {$PUSH} {$R-} pb.ioBuffer := @s[1]; pb.ioReqCount := len; pb.ioPosMode := fsFromStart; pb.ioPosOffset := pos; err := PBReadSync(@pb); if (err = eofErr) & (pb.ioActCount > 0) then begin err := noErr; end; if err = noErr then begin s[0] := chr(pb.ioActCount); end; {$POP} MyFSReadChunkPos := err; end; procedure MTDSetPort (var mtd: MyTextDisplayRecord); begin SetPort(mtd.window); TextFont(mtd.font); TextSize(mtd.size); TextFace([]); end; procedure MTDOffsetToLine (var mtd: MyTextDisplayRecord; offset: longInt; var thisline: longInt); var s, m, f: longInt; begin if offset <= 0 then begin thisline := 1; end else if offset >= mtd.total_length then begin thisline := mtd.lines; end else begin s := 1; f := mtd.lines + 1; while s < f do begin m := (f + s) div 2; if offset >= mtd.offsets^^[m] then s := m; if offset < mtd.offsets^^[m + 1] then f := m; if offset = mtd.offsets^^[m + 1] then begin { cheat to make it work with filelen } s := m + 1; leave; end; end; thisline := s; end; end; procedure MTDSetFontSize (var mtd: MyTextDisplayRecord; font, size: integer); begin mtd.font := font; mtd.size := size; if size = 0 then begin mtd.leading := 2; end else begin mtd.leading := size div 6; if mtd.leading = 0 then mtd.leading := 1; end; MTDSetPort(mtd); GetFontInfo(mtd.fi); mtd.line_height := mtd.fi.ascent + mtd.fi.descent + mtd.leading; end; procedure MTDSetControls (var mtd: MyTextDisplayRecord); var m: integer; begin mtd.vcontrol^^.contrlVis := invis; m := Max(0, mtd.lines - mtd.view_lines); SetCtlMax(mtd.vcontrol, m); mtd.top_line := Pin(0, mtd.top_line, m); SetCtlValue(mtd.vcontrol, mtd.top_line); mtd.vcontrol^^.contrlVis := vis; Draw1Control(mtd.vcontrol); if mtd.hcontrol <> nil then begin mtd.hcontrol^^.contrlVis := invis; m := Max(0, mtd.width - mtd.view_width); SetCtlMax(mtd.hcontrol, m); mtd.hoffset := Pin(0, mtd.hoffset, m); SetCtlValue(mtd.hcontrol, mtd.hoffset); mtd.hcontrol^^.contrlVis := vis; Draw1Control(mtd.hcontrol); end; end; procedure MTDRecalculate (var mtd: MyTextDisplayRecord; justappend: boolean); var err: OSErr; handlesize: longInt; pos, nextpos: longInt; offset, linebytes: longInt; filelen: longInt; line: str255; slbc: StyledLineBreakCode; textwidth: fixed; orgoffset: longInt; thisline: longInt; initialline: longInt; begin MTDSetPort(mtd); mtd.last_click_time := 0; handlesize := GetHandleSize(handle(mtd.offsets)) div 4; err := GetEOF(mtd.rn, filelen); mtd.total_length := filelen; if justappend & (mtd.lines > 1) then begin mtd.lines := mtd.lines - 1; pos := mtd.offsets^^[mtd.lines + 1]; orgoffset := maxLongInt; initialline := 0; {mtd.lines} end else begin orgoffset := mtd.offsets^^[Min(mtd.lines + 1, mtd.top_line + 1)]; mtd.lines := 0; pos := 0; initialline := 0; end; if err = noErr then begin err := MyFSReadLineAt(mtd.rn, pos, line); while err = noErr do begin nextpos := pos + length(line) + 1; offset := 0; while (offset = 0) or (offset < length(line)) do begin textwidth := BSL(mtd.width, 16); linebytes := 1; {$PUSH} {$R-} slbc := StyledLineBreak(@line[offset + 1], length(line) - offset, 0, length(line) - offset, 0, textwidth, linebytes); {$POP} mtd.lines := mtd.lines + 1; if mtd.lines > handlesize then begin handlesize := handlesize + 100; SetHandleSize(handle(mtd.offsets), handlesize * 4); end; mtd.offsets^^[mtd.lines] := pos + offset; if linebytes = 0 then begin offset := offset + 1; end else begin offset := offset + linebytes; end; end; pos := nextpos; err := MyFSReadLineAt(mtd.rn, pos, line); end; end; SetHandleSize(handle(mtd.offsets), (mtd.lines + 1) * 4); mtd.offsets^^[mtd.lines + 1] := filelen; mtd.hoffset := 0; MTDOffsetToLine(mtd, orgoffset, thisline); mtd.top_line := Max(0, Min(thisline - 1, mtd.lines - mtd.view_lines)); MTDSetControls(mtd); MTDDisplay(mtd, nil, initialline); end; function MTDLinePosToHOffset (var mtd: MyTextDisplayRecord; var line: str255; linepos: integer): integer; begin {$PUSH} {$R-} MTDLinePosToHOffset := Char2Pixel(@line[1], length(line), 0, linepos, 1) + mtd.view.left - mtd.hoffset; {$POP} end; function MTDHOffsetToLinePos (var mtd: MyTextDisplayRecord; var line: str255; hoffset: integer; var rightside: boolean): integer; var linepos: integer; begin {$PUSH} {$R-} linepos := Pixel2Char(@line[1], length(line), 0, hoffset, rightside); {$POP} rightside := rightside <> false; MTDHOffsetToLinePos := linepos; end; procedure MTDDisplay (var mtd: MyTextDisplayRecord; draw_region: RgnHandle; fromline: longInt); var line: str255; function LinePos (thisline, o: longInt): integer; var base: longInt; begin base := mtd.offsets^^[thisline]; if o <= base then begin LinePos := mtd.view.left; end else if o >= mtd.offsets^^[thisline + 1] then begin LinePos := mtd.view.right; end else begin LinePos := MTDLinePosToHOffset(mtd, line, o - base); end; end; var err: OSErr; v: integer; thisline: longInt; s, f: longInt; sh, fh: integer; oldclip: RgnHandle; r: rect; begin MTDSetPort(mtd); oldclip := NewRgn; GetClip(oldclip); if draw_region = nil then begin ClipRect(mtd.view); end else begin SectRectRgn(draw_region, mtd.view); SetClip(draw_region); end; v := mtd.view.top + mtd.fi.ascent; for thisline := mtd.top_line + 1 to Min(mtd.lines, mtd.top_line + mtd.view_lines) do begin if thisline >= fromline then begin err := MyFSReadChunkPos(mtd.rn, mtd.offsets^^[thisline], mtd.offsets^^[thisline + 1] - mtd.offsets^^[thisline], line); if err <> noErr then leave; r := mtd.view; r.top := v - mtd.fi.ascent - mtd.leading; r.bottom := v + mtd.fi.descent; MoveTo(mtd.view.left - mtd.hoffset, v); EraseRect(r); DrawString(line); s := mtd.selStart; f := mtd.selEnd; if (s < f) & (s < mtd.offsets^^[thisline + 1]) & (mtd.offsets^^[thisline] < f) then begin { Selection } sh := LinePos(thisline, s); fh := LinePos(thisline, f); BitClr(POINTER(HiliteMode), pHiliteBit); InvertRect(v - mtd.fi.ascent - mtd.leading, sh, v + mtd.fi.descent, fh); end; end; v := v + mtd.line_height; end; SetClip(oldclip); DisposeRgn(oldclip); end; procedure MTDScroll (var mtd: MyTextDisplayRecord; scroll: LongPoint); var update: RgnHandle; begin scroll.v := Pin(-mtd.top_line, scroll.v, Max(0, mtd.lines - mtd.top_line - mtd.view_lines)); scroll.h := Pin(-mtd.hoffset, scroll.h, Max(0, mtd.width - mtd.hoffset - mtd.view_width)); if (scroll.v <> 0) or (scroll.h <> 0) then begin update := NewRgn; ScrollRect(mtd.view, -scroll.h, -scroll.v * mtd.line_height, update); mtd.hoffset := mtd.hoffset + scroll.h; mtd.top_line := mtd.top_line + scroll.v; MTDDisplay(mtd, update, 0); DisposeRgn(update); MTDSetControls(mtd); end; end; {WARNING: Only really valid for pts inside mtd.view! } procedure MTDPointToOffset (var mtd: MyTextDisplayRecord; pt: Point; var thisline, offset: longInt; var rightside: boolean; var line: str255; var scroll: LongPoint); var last_line: longInt; h: integer; err: OSErr; begin rightside := false; scroll.h := 0; scroll.v := 0; line := ''; last_line := Min(mtd.top_line + mtd.view_lines, mtd.lines); if pt.v < mtd.view.top then begin scroll.v := -((mtd.view.top - pt.v) div mtd.line_height + 1); offset := mtd.offsets^^[mtd.top_line + 1]; thisline := mtd.top_line + 1; end else if pt.v > mtd.view.bottom then begin scroll.v := (pt.v - mtd.view.bottom) div mtd.line_height + 1; offset := mtd.offsets^^[last_line + 1]; thisline := last_line; rightside := false; end else begin if pt.h < mtd.view.left then begin scroll.h := pt.h - mtd.view.left; end else if pt.h > mtd.view.right then begin scroll.h := pt.h - mtd.view.right; end; thisline := mtd.top_line + (pt.v - mtd.view.top) div mtd.line_height + 1; if thisline > mtd.lines then begin thisline := mtd.lines + 1; offset := mtd.total_length; rightside := false; end else begin h := Max(0, pt.h - mtd.view.left + mtd.hoffset); err := MyFSReadChunkPos(mtd.rn, mtd.offsets^^[thisline], mtd.offsets^^[thisline + 1] - mtd.offsets^^[thisline], line); offset := MTDHOffsetToLinePos(mtd, line, h, rightside); if offset >= length(line) then begin offset := length(line); rightside := false; end; offset := mtd.offsets^^[thisline] + offset; end; end; end; procedure MTDReadLine (var mtd: MyTextDisplayRecord; theline: longInt; var line: str255); var err: OSErr; begin line := ''; if theline <= mtd.lines then err := MyFSReadChunkPos(mtd.rn, mtd.offsets^^[theline], mtd.offsets^^[theline + 1], line); end; procedure MTDOffsetToPoint (var mtd: MyTextDisplayRecord; offset: longInt; var pt: Point); var thisline: longInt; h: integer; line: str255; begin MTDOffsetToLine(mtd, offset, thisline); if thisline <= mtd.top_line then begin pt := mtd.view.topleft; pt.v := pt.v - mtd.line_height; end else if thisline > mtd.top_line + mtd.view_lines + 1 then begin pt := mtd.view.botright; pt.v := pt.v + mtd.line_height; end else begin MTDReadLine(mtd, thisline, line); pt.v := (thisline - mtd.top_line - 1) * mtd.line_height + mtd.leading + mtd.fi.ascent; pt.h := MTDLinePosToHOffset(mtd, line, offset - mtd.offsets^^[thisline]); end; end; procedure UnionRectRgn (rgn: RgnHandle; l, t, r, b: integer); var rrgn: RgnHandle; begin rrgn := NewRgn; SetRectRgn(rrgn, l, t, r, b); UnionRgn(rgn, rrgn, rgn); DisposeRgn(rrgn); end; procedure MTDGetSelectionData (var mtd: MyTextDisplayRecord; h: handle); var err: OSErr; begin HUnlock(h); SetHandleSize(h, 0); SetHandleSize(h, mtd.selEnd - mtd.selStart); err := MyFSReadAt(mtd.rn, mtd.selStart, GetHandleSize(h), h^); if err <> noErr then begin SetHandleSize(h, 0); end; end; procedure MTDSetSelection (var mtd: MyTextDisplayRecord; start, fin: longInt); function InView (v: integer): boolean; begin InView := (mtd.view.top <= v) & (v <= mtd.view.bottom); end; procedure GetSelRgn (s, f: longInt; r: RgnHandle); var sp, fp: Point; ascent, descent, left, right, top, bottom: integer; t, b: integer; begin if s < f then begin MTDOffsetToPoint(mtd, s, sp); MTDOffsetToPoint(mtd, f, fp); ascent := mtd.fi.ascent + mtd.leading; descent := mtd.fi.descent; left := mtd.view.left; right := mtd.view.right; top := mtd.view.top; bottom := mtd.view.bottom; if sp.v = fp.v then begin if InView(sp.v) then begin SetRectRgn(r, sp.h, sp.v - ascent, fp.h, sp.v + descent); end; end else begin if InView(sp.v) then begin SetRectRgn(r, sp.h, sp.v - ascent, right, sp.v + descent); t := sp.v + descent; end else begin t := top; end; if InView(fp.v) then begin UnionRectRgn(r, left, fp.v - ascent, fp.h, fp.v + descent); b := fp.v - ascent; end else begin b := bottom; end; UnionRectRgn(r, left, t, right, b); end; end; SectRectRgn(r, mtd.view); end; var orgn, nrgn: RgnHandle; begin if (start <> mtd.selStart) or (fin <> mtd.selEnd) then begin MTDSetPort(mtd); orgn := NewRgn; nrgn := NewRgn; GetSelRgn(mtd.selStart, mtd.selEnd, orgn); mtd.selStart := start; mtd.selEnd := fin; GetSelRgn(mtd.selStart, mtd.selEnd, nrgn); XorRgn(orgn, nrgn, nrgn); BitClr(POINTER(HiliteMode), pHiliteBit); InvertRgn(nrgn); DisposeRgn(nrgn); DisposeRgn(orgn); end; end; procedure MTDResize (var mtd: MyTextDisplayRecord; view: rect); begin mtd.vcontrol^^.contrlVis := invis; if mtd.hcontrol <> nil then begin mtd.hcontrol^^.contrlVis := invis; end; EraseRect(mtd.full_rect); InvalRect(mtd.full_rect); mtd.full_rect := view; mtd.view := view; mtd.view.right := view.right - 16; if (mtd.hcontrol <> nil) then begin mtd.view.bottom := mtd.view.bottom - 16; end; InsetRect(mtd.view, mtd.leading, mtd.leading); mtd.view_lines := (mtd.view.bottom - mtd.view.top) div mtd.line_height; mtd.view.bottom := mtd.view.top + mtd.view_lines * mtd.line_height - mtd.leading; mtd.view_width := mtd.view.right - mtd.view.left; if mtd.width = 0 then begin mtd.width := mtd.view_width; end; MoveControl(mtd.vcontrol, view.right - 15, view.top - 1); SizeControl(mtd.vcontrol, 16, view.bottom - view.top - 16 * ord(mtd.leave_room_for_grow) + 3); if mtd.hcontrol <> nil then begin MoveControl(mtd.hcontrol, view.left - 1, view.bottom - 15); SizeControl(mtd.hcontrol, view.right - view.left - 13, 16); end; MTDRecalculate(mtd, false); end; procedure MTDCreate (var mtd: MyTextDisplayRecord; window: WindowPtr; rn: integer; width: integer; hcontrol: boolean); var bounds: rect; begin mtd.window := window; SetRect(mtd.view, 0, 0, 0, 0); mtd.width := width; mtd.leave_room_for_grow := true; mtd.rn := rn; mtd.lines := 0; mtd.total_length := 0; mtd.top_line := 0; mtd.hoffset := 0; mtd.selStart := 0; mtd.selEnd := 0; mtd.last_click_time := 0; mtd.offsets := LongArrayHandle(NewHandleClear(4)); SetRect(bounds, 0, 0, 15, 100); mtd.vcontrol := NewControl(window, bounds, '', false, 0, 0, 0, scrollBarProc, ord(@mtd)); if hcontrol then begin SetRect(bounds, 0, 0, 100, 15); mtd.hcontrol := NewControl(window, bounds, '', false, 0, 0, 0, scrollBarProc, ord(@mtd)); end else begin mtd.hcontrol := nil; end; MTDSetFontSize(mtd, 0, 0); end; var action_mte: ^MyTextDisplayRecord; action_amount: LongPoint; procedure MTDActionProc (control: ControlHandle; part: integer); var amount: integer; window: WindowPtr; begin if (part <> 0) then begin MTDScroll(action_mte^, action_amount); end; end; procedure GetActionAmount (var mtd: MyTextDisplayRecord; control: ControlHandle; part: integer; var scroll: LongPoint); var amount, amount_pg, amount_line: integer; begin if control = mtd.vcontrol then begin amount_pg := mtd.view_lines - 1; amount_line := 1; end else begin amount_pg := mtd.view_width; amount_line := 8; { a few pixels } end; case part of inUpButton: amount := -amount_line; inDownButton: amount := amount_line; inPageUp: amount := -amount_pg; inPageDown: amount := amount_pg; otherwise amount := 0; end; if control = mtd.vcontrol then begin scroll.h := 0; scroll.v := amount; end else begin scroll.h := amount; scroll.v := 0; end; end; procedure MTDDoClick (var mtd: MyTextDisplayRecord; var er: EventRecord); var click_type: (CT_First, CT_Double, CT_Tripple); rightside: boolean; thisline: longInt; line: str255; procedure GetCurrentPos (offset: longInt; var s, f: longInt); var base: longInt; offtab: OffsetTable; begin base := mtd.offsets^^[thisline]; case click_type of CT_First: begin s := offset + ord(rightside); f := offset + ord(rightside); end; CT_Double: begin {$PUSH} {$R-} FindWord(@line[1], length(line), offset - base, rightside, nil, offtab); {$POP} s := base + offtab[0].offFirst; f := base + offtab[0].offSecond; end; CT_Tripple: begin s := base; if thisline <= mtd.lines then begin f := mtd.offsets^^[thisline + 1]; end else begin f := base; end; end; end; { case } end; var pt: Point; control: ControlHandle; part: integer; scroll: LongPoint; offset, ancors, ancorf, s, f, value: longInt; shift: boolean; lastoffset: longint; amount: longInt; begin MTDSetPort(mtd); pt := er.where; GlobalToLocal(pt); if PtInRect(pt, mtd.view) then begin shift := BAND(er.modifiers, shiftKey) <> 0; MTDPointToOffset(mtd, pt, thisline, offset, rightside, line, scroll); if not shift & (er.when - mtd.last_click_time <= GetDblTime) & (offset = mtd.last_click_offset) then begin if mtd.double_click then begin click_type := CT_Tripple; end else begin click_type := CT_Double; end; mtd.double_click := true; end else begin click_type := CT_First; mtd.double_click := false; mtd.last_click_offset := offset; end; if not shift then begin GetCurrentPos(offset, ancors, ancorf); end else begin if mtd.selStart < mtd.selEnd then begin if offset > mtd.selStart then begin ancors := mtd.selStart; ancorf := mtd.selStart; end else begin ancors := mtd.selEnd; ancorf := mtd.selEnd; end; end else begin ancors := offset; ancorf := offset; end; end; MTDSetSelection(mtd, ancors, ancorf); while StillDown do begin GetMouse(pt); MTDPointToOffset(mtd, pt, thisline, offset, rightside, line, scroll); GetCurrentPos(offset, s, f); MTDSetSelection(mtd, Min(ancors, s), Max(ancorf, f)); MTDScroll(mtd, scroll); end; mtd.last_click_time := TickCount; end else begin part := FindControl(pt, mtd.window, control); if part <> 0 then begin if part = inThumb then begin value := GetCtlValue(control); part := TrackControl(control, pt, nil); if part <> 0 then begin amount := GetCtlValue(control) - value; if amount <> 0 then begin if control = mtd.vcontrol then begin scroll.v := amount; scroll.h := 0; end else begin scroll.h := amount; scroll.v := 0; end; MTDScroll(mtd, scroll); end; end; end else begin GetActionAmount(mtd, control, part, action_amount); action_mte := @mtd; value := TrackControl(control, pt, @MTDActionProc) end; end else begin SysBeep(1); end; end; end; procedure MTDDoKey (var mtd: MyTextDisplayRecord; ch: char); var scroll: LongPoint; begin scroll.h := 0; scroll.v := 0; case ord(ch) of homeChar: begin scroll.v := -mtd.lines; end; endChar: begin scroll.v := mtd.lines; end; pageUpChar: begin GetActionAmount(mtd, mtd.vcontrol, inPageUp, scroll); end; pageDownChar: begin GetActionAmount(mtd, mtd.vcontrol, inPageDown, scroll); end; otherwise SysBeep(1); end; MTDScroll(mtd, scroll); end; procedure MTDSetMouse (var mtd: MyTextDisplayRecord); var pt: point; begin SetPort(mtd.window); GetMouse(pt); if PtInRect(pt, mtd.view) then begin SetCursor(GetCursor(iBeamCursor)^^); end else begin SetCursor(arrow); end; end; procedure MTDDestroy (var mtd: MyTextDisplayRecord); begin DisposeHandle(handle(mtd.offsets)); { DisposeControl(mtd.vcontrol);} if mtd.hcontrol <> nil then begin { DisposeControl(mtd.hcontrol);} end; end; end.